-
Notifications
You must be signed in to change notification settings - Fork 29.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
src: track memory retainer fields #26161
Conversation
src/memory_tracker-inl.h
Outdated
@@ -81,6 +81,10 @@ void MemoryTracker::TrackFieldWithSize(const char* edge_name, | |||
if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name); | |||
} | |||
|
|||
void MemoryTracker::TrackInlineField(const MemoryRetainer& field) { | |||
field.MemoryInfo(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like if we want to provide this as an utility, it might make sense to perform something similar to Track()
and/or re-use it? That would be nice if we do want to see these fields as full “objects” on their own, and want to show them that way in heap dumps.
Then again, I think you’re making a good point about this approach being a good way to address the problem itself; I just didn’t think of a direct approach like this. 😄 Adding 2 calls à la thread_stopper_.MemoryInfo(tracker);
in #26099 would also be a good way to address this, as far as that PR is concerned in particular?
56f5969
to
6055693
Compare
@addaleax - pushed a change; ptal. I tested the two inline fields in question using { name: 'Node / Worker',
isRoot: true,
size: 200,
edges:
[ { name: 'wrapped',
to:
{ name: '<JS Node>',
isRoot: false,
size: 0,
edges: [Array],
value: [Worker] } },
{ name: 'parent_port',
to:
{ name: 'Node / MessagePort',
isRoot: true,
size: 88,
edges: [Array] } },
{ name: 'on_thread_finished_',
to:
{ name: 'Node / AsyncRequest',
isRoot: false,
size: 72,
edges: [Array] } },
{ name: 'thread_stopper_',
to:
{ name: 'Node / AsyncRequest',
isRoot: false,
size: 72,
edges: [Array] } } ] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, LGTM!
CI is green … Maybe somebody has time to give this a second review? |
// be one, of the container object which the current field is part of. | ||
// Reduce the size of memory from the container so that retentions | ||
// are accounted properly. | ||
inline void TrackInlineField(const MemoryRetainer* retainer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what this is trying to achieve, is there a use case of this API?
Normally the size of the pointer to the retainer should be accounted into the self size of the parent but self size of the retainer should not be accounted, so I can't tell when it's necessary to do a subtraction - maybe there is a misuse of the API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is trying to address the issues that came up from #26099 there are two ways to solve that:
- Do not use
SET_SELF_SIZE(Worker)
, which is merely a convenience macro that implementsWorker::SelfSize()
withsizeof(Worker)
to calculate the self size of the worker - that's where we encounter the first tracking of the inline field. Instead, overrideWorker::SelfSize()
to exclude the size of the non-pointer fields, and track them later inWorker::MemoryInfo()
. - Keep using
SET_SELF_SIZE(Worker)
for the initial calculation, but rename this method into something likeSplitNonPointerField()
(SplitStackAllocatedField()
?), because this does not necessarily add more memory into the tracker - instead it's splitting off existing memory that have already been tracked, and potentially adding more (if theretainer
overridesMemoryInfo()
to add additional fields that are not accounted by asizeof()
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joyeecheung - thanks.
Probably these many scenarios exists (with a component cmp
and a container cnt
with cnt
a retainer):
cmp
is a normal primitive field: self size ofcnt
covers size ofcmp
cmp
is a composite object (with or without primitive fields): self size ofcnt
covers size ofcmp
cmp
is a pointer to a composite object: self size ofcnt
covers size ofcmp
(size of a pointer)cmp
is a composite object (with pointer type fields): self size ofcnt
covers size ofcmp
, not the dynamic memory [ example: messageport in worker ]cmp
is a composite retainer object (with pointer types): [ current use case ]cmp
is a pointer to a retainer object (with pointer types): [ none at the moment? ]
We have Worker
with 200 bytes excluding 2 inline AsyncRequest
fields of 72 bytes each. So the question is: should we add those to the worker (because those belongs to worker) or add separately - as they are retainers themselves.
If I follow your approach 1, we need to perform that in all such scenarios? in which case arguably the parent retainer (Worker's referencing object if any) could do the same for worker, and potentially render the Retainer abstraction meaningless?
second proposal looks fair to me, but would love to hear from @addaleax as well. Can SplitStackAllocatedField
be somewhat misleading, as there is no memory that is stack-allocated here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because this does not necessarily add more memory into the tracker - instead it's splitting off existing memory that have already been tracked, and potentially adding more (if the
retainer
overridesMemoryInfo()
to add additional fields that are not accounted by asizeof()
)
I’m not sure, but I don’t really see this as a issue/as not “tracking” these fields? Then again, re-naming is not a big deal, so I’m okay with anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we add those to the worker (because those belongs to worker) or add separately - as they are retainers themselves.
That depends on whether we want to display AsyncRequest separately as a Node referenced by the Worker node, and any other fields referenced by those AsyncRequests recursively.
If I follow your approach 1, we need to perform that in all such scenarios? in which case arguably the parent retainer (Worker's referencing object if any) could do the same for worker, and potentially render the Retainer abstraction meaningless?
The abstraction helps adding nodes recursively instead of tracking everything from the root, allowing the MemoryRetainer implementations define how they want their fields to be tracked.
Can
SplitStackAllocatedField
be somewhat misleading, as there is no memory that is stack-allocated here?
Right, the fields are not really stack allocated if the whole thing is not stack allocated.
I’m not sure, but I don’t really see this as a issue/as not “tracking” these fields?
I am fine with the current naming if the comments are changed to reflect when this method should be used and what issue it is trying to address. Use when a retainer is embedded in another.
and Reduce the size of memory from the container so that retentions are accounted properly.
is too ambiguous IMO, the core of the issue this method is trying to solve arise from that the parent does not implement its SelfSize()
correctly to exclude the memory that is supposed to be tracked as the self size of another node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joyeecheung - thanks, I will amend the comments, aligning with above conversation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joyeecheung - amended the comment, ptal!
If retainers are embedded in retainers, direct tracking those lead to double tracking. Instead, use a special tracker that adjusts the tracking for the container object. PR-URL: nodejs#26161 Reviewed-By: Anna Henningsen <[email protected]>
726350f
to
17b4949
Compare
landed as 17b4949 |
If retainers are embedded in retainers, direct tracking those lead to double tracking. Instead, use a special tracker that adjusts the tracking for the container object. PR-URL: #26161 Reviewed-By: Anna Henningsen <[email protected]>
If retainers are embedded in retainers, direct tracking
those lead to double tracking. Instead, use the base tracker
to track the field objects.
cc @addaleax [ I should admit that I don't have full understanding of this feature, so let me know if this needs rewrite ]
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes